/*
 * Dump RV64 code in text format!
 */
#include "HRV64Generator.h"
#include "HXD1.h"

/*
 * RV64 instruction names. For `AND`, `LOAD`, etc,
 * it is just a prefix, and the suffix is the size of
 * the operation.
 */
const char * const RV64Inst_name[] = {
	[uint32_t(RV64Oper::NOP)] = "nop",
	[uint32_t(RV64Oper::ADD)] = "add",
	[uint32_t(RV64Oper::SUB)] = "sub",
	[uint32_t(RV64Oper::AND)] = "and",
	[uint32_t(RV64Oper::OR)] = "or",
	[uint32_t(RV64Oper::XOR)] = "xor",
	[uint32_t(RV64Oper::SLL)] = "sll",
	[uint32_t(RV64Oper::SRA)] = "sra",
	[uint32_t(RV64Oper::SRL)] = "srl",
	[uint32_t(RV64Oper::ADDi)] = "addi",
	[uint32_t(RV64Oper::ANDi)] = "andi",
	[uint32_t(RV64Oper::ORi)] = "ori",
	[uint32_t(RV64Oper::XORi)] = "xori",
	[uint32_t(RV64Oper::SLLi)] = "slli",
	[uint32_t(RV64Oper::SRAi)] = "srai",
	[uint32_t(RV64Oper::SRLi)] = "srli",
	[uint32_t(RV64Oper::SLT)] = "slt",
	[uint32_t(RV64Oper::SLTu)] = "sltu",
	[uint32_t(RV64Oper::SLTi)] = "slti",
	[uint32_t(RV64Oper::SLTiu)] = "sltiu",
	[uint32_t(RV64Oper::LOAD)] = "l",
	[uint32_t(RV64Oper::STORE)] = "s",
	[uint32_t(RV64Oper::LA)] = "la",
	[uint32_t(RV64Oper::LDs)] = "l",
	[uint32_t(RV64Oper::LI)] = "li",
	[uint32_t(RV64Oper::CALL)] = "call",
	[uint32_t(RV64Oper::RET)] = "ret",
	[uint32_t(RV64Oper::J)] = "j",
	[uint32_t(RV64Oper::BEQ)] = "beq",
	[uint32_t(RV64Oper::BNE)] = "bne",
	[uint32_t(RV64Oper::BLT)] = "blt",
	[uint32_t(RV64Oper::BLTu)] = "bltu",
	[uint32_t(RV64Oper::BGE)] = "bge",
	[uint32_t(RV64Oper::BGEu)] = "bgeu",
	[uint32_t(RV64Oper::BLE)] = "ble",
	[uint32_t(RV64Oper::BLEu)] = "bleu",
	[uint32_t(RV64Oper::BGT)] = "bgt",
	[uint32_t(RV64Oper::BGTu)] = "bgtu",
	[uint32_t(RV64Oper::MV)] = "mv",
	[uint32_t(RV64Oper::MUL)] = "mul",
	[uint32_t(RV64Oper::DIV)] = "div",
	[uint32_t(RV64Oper::REM)] = "rem",
	[uint32_t(RV64Oper::FLOAD)] = "fl",
	[uint32_t(RV64Oper::FSTORE)] = "fs",
	[uint32_t(RV64Oper::FADD)] = "fadd.",
	[uint32_t(RV64Oper::FSUB)] = "fsub.",
	[uint32_t(RV64Oper::FMUL)] = "fmul.",
	[uint32_t(RV64Oper::FDIV)] = "fdiv.",
	[uint32_t(RV64Oper::FMIN)] = "fmin.",
	[uint32_t(RV64Oper::FMAX)] = "fmax.",
	[uint32_t(RV64Oper::FMV)] = "fmv.",
	[uint32_t(RV64Oper::FNEG)] = "fneg.",
	[uint32_t(RV64Oper::FABS)] = "fabs.",
	[uint32_t(RV64Oper::FMV2I)] = "fmv.x.",
	[uint32_t(RV64Oper::FMV4I)] = "fmv.",
	[uint32_t(RV64Oper::FLT)] = "flt.",
	[uint32_t(RV64Oper::FLE)] = "fle.",
	[uint32_t(RV64Oper::FGT)] = "fgt.",
	[uint32_t(RV64Oper::FGE)] = "fge.",
	[uint32_t(RV64Oper::FEQ)] = "feq.",
	[uint32_t(RV64Oper::LABEL)] = "L"
};

extern const char *const RV64GFR_name[] = {
	[uint32_t(RV64GFR::ZERO)] = "zero",
	[uint32_t(RV64GFR::RA)] = "ra",
	[uint32_t(RV64GFR::SP)] = "sp",
	[uint32_t(RV64GFR::GP)] = "gp",
	[uint32_t(RV64GFR::TP)] = "tp",
	[uint32_t(RV64GFR::T0)] = "t0",
	[uint32_t(RV64GFR::T1)] = "t1",
	[uint32_t(RV64GFR::T2)] = "t2",
	[uint32_t(RV64GFR::S0)] = "s0",
	[uint32_t(RV64GFR::S1)] = "s1",
	[uint32_t(RV64GFR::A0)] = "a0",
	[uint32_t(RV64GFR::A1)] = "a1",
	[uint32_t(RV64GFR::A2)] = "a2",
	[uint32_t(RV64GFR::A3)] = "a3",
	[uint32_t(RV64GFR::A4)] = "a4",
	[uint32_t(RV64GFR::A5)] = "a5",
	[uint32_t(RV64GFR::A6)] = "a6",
	[uint32_t(RV64GFR::A7)] = "a7",
	[uint32_t(RV64GFR::S2)] = "s2",
	[uint32_t(RV64GFR::S3)] = "s3",
	[uint32_t(RV64GFR::S4)] = "s4",
	[uint32_t(RV64GFR::S5)] = "s5",
	[uint32_t(RV64GFR::S6)] = "s6",
	[uint32_t(RV64GFR::S7)] = "s7",
	[uint32_t(RV64GFR::S8)] = "s8",
	[uint32_t(RV64GFR::S9)] = "s9",
	[uint32_t(RV64GFR::S10)] = "s10",
	[uint32_t(RV64GFR::S11)] = "s11",
	[uint32_t(RV64GFR::T3)] = "t3",
	[uint32_t(RV64GFR::T4)] = "t4",
	[uint32_t(RV64GFR::T5)] = "t5",
	[uint32_t(RV64GFR::T6)] = "t6",
	[uint32_t(RV64GFR::FT0)] = "ft0",
	[uint32_t(RV64GFR::FT1)] = "ft1",
	[uint32_t(RV64GFR::FT2)] = "ft2",
	[uint32_t(RV64GFR::FT3)] = "ft3",
	[uint32_t(RV64GFR::FT4)] = "ft4",
	[uint32_t(RV64GFR::FT5)] = "ft5",
	[uint32_t(RV64GFR::FT6)] = "ft6",
	[uint32_t(RV64GFR::FT7)] = "ft7",
	[uint32_t(RV64GFR::FS0)] = "fs0",
	[uint32_t(RV64GFR::FS1)] = "fs1",
	[uint32_t(RV64GFR::FA0)] = "fa0",
	[uint32_t(RV64GFR::FA1)] = "fa1",
	[uint32_t(RV64GFR::FA2)] = "fa2",
	[uint32_t(RV64GFR::FA3)] = "fa3",
	[uint32_t(RV64GFR::FA4)] = "fa4",
	[uint32_t(RV64GFR::FA5)] = "fa5",
	[uint32_t(RV64GFR::FA6)] = "fa6",
	[uint32_t(RV64GFR::FA7)] = "fa7",
	[uint32_t(RV64GFR::FS2)] = "fs2",
	[uint32_t(RV64GFR::FS3)] = "fs3",
	[uint32_t(RV64GFR::FS4)] = "fs4",
	[uint32_t(RV64GFR::FS5)] = "fs5",
	[uint32_t(RV64GFR::FS6)] = "fs6",
	[uint32_t(RV64GFR::FS7)] = "fs7",
	[uint32_t(RV64GFR::FS8)] = "fs8",
	[uint32_t(RV64GFR::FS9)] = "fs9",
	[uint32_t(RV64GFR::FS10)] = "fs10",
	[uint32_t(RV64GFR::FS11)] = "fs11",
	[uint32_t(RV64GFR::FT8)] = "ft8",
	[uint32_t(RV64GFR::FT9)] = "ft9",
	[uint32_t(RV64GFR::FT10)] = "ft10",
	[uint32_t(RV64GFR::FT11)] = "ft11"
};


const char RV64Inst_suffix[] = {
	// to show an error
	[uint32_t(RV64Opsz::DEFAULT)] = '*',
	[uint32_t(RV64Opsz::B)] = 'b',
	[uint32_t(RV64Opsz::H)] = 'h',
	[uint32_t(RV64Opsz::W)] = 'w',
	[uint32_t(RV64Opsz::S)] = 's',
	[uint32_t(RV64Opsz::D)] = 'd',
};

// string representing an error
static const char * const Error = "**Error**";

// Print a RV64 instruction. Index of the function is needed for label.
void HRV64Generator::print_inst(const RV64Inst &inst, size_t funcidx, std::ostream& os)
{
	const char *opname = RV64Inst_name[uint32_t(inst.oper)];
	const std::string Label_prefix = ".Lf" + std::to_string(funcidx) + 'L';
	if (inst.oper != RV64Oper::LABEL)
		os << '\t';
	switch (inst.oper)
	{
	case RV64Oper::NOP:
		os << opname;
		break;
	case RV64Oper::ADD: case RV64Oper::SUB: case RV64Oper::AND: case RV64Oper::OR:
	case RV64Oper::XOR: case RV64Oper::SLL: case RV64Oper::SRA: case RV64Oper::SRL:
	case RV64Oper::MUL: case RV64Oper::DIV: case RV64Oper::REM:
	case RV64Oper::SLT: case RV64Oper::SLTu:
		os << opname;
		if (inst.size != RV64Opsz::D)
			os << RV64Inst_suffix[uint32_t(inst.size)];
		os << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", "
			<< RV64GFR_name[uint32_t(inst.src1)] << ", "
			<< RV64GFR_name[uint32_t(inst.src2)];
		break;
	case RV64Oper::ADDi: case RV64Oper::ANDi: case RV64Oper::ORi: case RV64Oper::XORi:
	case RV64Oper::SLLi: case RV64Oper::SRAi: case RV64Oper::SRLi:
	case RV64Oper::SLTi: case RV64Oper::SLTiu:
		os << opname;
		if (inst.size != RV64Opsz::D)
			os << RV64Inst_suffix[uint32_t(inst.size)];
		os << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", "
			<< RV64GFR_name[uint32_t(inst.src1)] << ", "
			<< inst.imm12;
		break;
	case RV64Oper::LOAD: case RV64Oper::STORE:
		os << opname << RV64Inst_suffix[uint32_t(inst.size)] << '\t';
		os << (inst.oper == RV64Oper::LOAD 
			? RV64GFR_name[uint32_t(inst.dest)]
			: RV64GFR_name[uint32_t(inst.src2)]) << ", "
			<< inst.off12 << '(' << RV64GFR_name[uint32_t(inst.base)] << ')';
		break;
	case RV64Oper::LA: case RV64Oper::LDs:
		os << opname;
		if (inst.oper == RV64Oper::LDs)
			os << RV64Inst_suffix[uint32_t(inst.size)];
		os << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", ";
		os << xsyms_.at(inst.xsymidx).id;
		break;
	case RV64Oper::LI:
		os << opname << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", "
			<< inst.imm64;
		break;
	case RV64Oper::J:
		os << opname << '\t';
		os << Label_prefix << std::to_string(inst.labelno);
		if (opts.debug_level != 0 && inst.labelno == funcs_.at(funcidx).epilogue())
			os << "\t\t# return";
		break;
	case RV64Oper::CALL:
		os << opname << '\t';
		os << funcs_.at(inst.functidx).id();
		break;
	case RV64Oper::BEQ: case RV64Oper::BNE: case RV64Oper::BLT: case RV64Oper::BLTu:
	case RV64Oper::BGE: case RV64Oper::BGEu: case RV64Oper::BLE: case RV64Oper::BLEu:
	case RV64Oper::BGT: case RV64Oper::BGTu:
		os << opname << '\t';
		os << RV64GFR_name[uint32_t(inst.src1)] << ", "
			<< RV64GFR_name[uint32_t(inst.src2)] << ", "
			<< Label_prefix << std::to_string(inst.labelno);
		break;
	case RV64Oper::RET:
		os << opname;
		break;
	case RV64Oper::MV:
		os << opname << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", "
			<< RV64GFR_name[uint32_t(inst.src1)];
		break;
	case RV64Oper::FLOAD: case RV64Oper::FSTORE:
		os << opname << RV64Inst_suffix[
			uint32_t(inst.size == RV64Opsz::S ? RV64Opsz::W : inst.size)] << '\t';
		os << (inst.oper == RV64Oper::FLOAD 
			? RV64GFR_name[uint32_t(inst.fdest)]
			: RV64GFR_name[uint32_t(inst.fsrc2)]) << ", "
			<< inst.off12 << '(' << RV64GFR_name[uint32_t(inst.base)] << ')';
		break;
	case RV64Oper::FADD: case RV64Oper::FSUB: case RV64Oper::FMUL: case RV64Oper::FDIV:
	case RV64Oper::FMIN: case RV64Oper::FMAX:
		os << opname << RV64Inst_suffix[uint32_t(inst.size)] << '\t';
		os << RV64GFR_name[uint32_t(inst.fdest)] << ", "
			<< RV64GFR_name[uint32_t(inst.fsrc1)] << ", "
			<< RV64GFR_name[uint32_t(inst.fsrc2)];
		break;
	case RV64Oper::FMV: case RV64Oper::FNEG: case RV64Oper::FABS:
		os << opname << RV64Inst_suffix[uint32_t(inst.size)] << '\t';
		os << RV64GFR_name[uint32_t(inst.fdest)] << ", "
			<< RV64GFR_name[uint32_t(inst.fsrc1)];
		break;
	case RV64Oper::FMV2I:
		os << opname << RV64Inst_suffix[uint32_t(inst.size)] << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", "
			<< RV64GFR_name[uint32_t(inst.fsrc1)];
		break;
	case RV64Oper::FMV4I:
		os << opname << RV64Inst_suffix[uint32_t(inst.size)]
			<< '.' << 'x' << '\t';
		os << RV64GFR_name[uint32_t(inst.fdest)] << ", "
			<< RV64GFR_name[uint32_t(inst.src1)];
		break;
	case RV64Oper::FLT: case RV64Oper::FLE: case RV64Oper::FGT: case RV64Oper::FGE:
	case RV64Oper::FEQ:
		os << opname << RV64Inst_suffix[uint32_t(inst.size)] << '\t';
		os << RV64GFR_name[uint32_t(inst.dest)] << ", "
			<< RV64GFR_name[uint32_t(inst.fsrc1)] << ", "
			<< RV64GFR_name[uint32_t(inst.fsrc2)];
		break;
	case RV64Oper::LABEL:
		os << Label_prefix << std::to_string(inst.labelno) << ':';
		break;
	default:
		os << Error;
		break;
	}
	if (inst.cominfo != nullptr)
		os << "\t# " << *inst.cominfo;
	os << std::endl;
}

void HRV64Generator::print_functions(std::ostream& os)
{
	os << ".text" << std::endl << ".align\t1" << std::endl;
	for (size_t i = nbuiltin_in_IR_; i < funcs_.size(); ++i)
	{
		// function header
		os << ".globl" << '\t' << funcs_.at(i).id() << std::endl;
		os << ".type" << '\t' << funcs_.at(i).id() << ", " << "@function" << std::endl;
		if (opts.debug_level != 0)
		{
			os << "# _frame_base: 0x" << std::hex
				<< funcs_.at(i).frame_base() << std::endl;
			os << "# _static_top: 0x" << std::hex
				<< funcs_.at(i).static_top() << std::endl;
			os << "# _common_sp : 0x" << std::hex
				<< funcs_.at(i).common_sp() << std::endl;
			for (size_t bbidx = 0ul; bbidx < funcs_.at(i).bb_stacks().size(); ++bbidx)
				os << "# BB #" << std::dec << bbidx << " _bb_sp: 0x" << std::hex
					<< funcs_.at(i).bb_stacks().at(bbidx).sp << std::endl;
		}
		os << std::dec;
		// print the function
		os << funcs_.at(i).id() << ':' << std::endl;
		for (auto & inst : funcs_.at(i).code())
			print_inst(inst, i, os);
		os << std::endl;
		// print interference graph
		if (opts.debug_level > 1 && opts.opt_gra != 0)
		{
			funcs_.at(i).intgraph().print(os, '#');
			funcs_.at(i).workingintgraph().print(os, '#');
		}
	}
}

// Print an external variable without `.section`!
void HRV64Generator::print_xvar(const RV64XSymbol& xvar, std::ostream& os)
{
	size_t i;
	os << ".align" << ' ' << xvar.align << std::endl;
	if (xvar.sttype == RV64XSymbol::BSS)
		os << ".lcomm" << ' ' << xvar.id << ", " << xvar.size << std::endl;
	else
	{
		if (xvar.data == nullptr)
			throw std::logic_error(xvar.id + ": no data but doesn't in .bss");
		os << ".size" << '\t' << xvar.id << ", " << xvar.size << std::endl;
		os << xvar.id << ':' << std::endl;
		if (xvar.size % 8u == 0u && uint64_t(xvar.data) % 8ul == 0ul)
		{
			const int64_t *p = (const int64_t*)(xvar.data);
			for (i = 0ul; i < xvar.size / 8u; ++i)
				os << '\t' << ".quad" << '\t' << p[i] << std::endl;
		}
		else if (xvar.size % 4u == 0u && uint64_t(xvar.data) % 4ul == 0ul)
		{
			const int32_t *p = (const int32_t*)(xvar.data);
			for (i = 0ul; i < xvar.size / 4u; ++i)
				os << '\t' << ".word" << '\t' << p[i] << std::endl;
		}
		else
		{
			const int8_t *p = (const int8_t*)(xvar.data);
			for (i = 0ul; i < xvar.size; ++i)
				os << '\t' << ".byte" << '\t' << int(p[i]) << std::endl;
		}
	}
}

void HRV64Generator::print_data(std::ostream& os)
{
	size_t i;
	// indexes of external variables
	std::vector<size_t> data, rodata, bss;
	for (i = 0ul; i < xsyms_.size(); ++i)
	{
		if (xsyms_.at(i).sttype == RV64XSymbol::DATA)
			data.push_back(i);
		else if (xsyms_.at(i).sttype == RV64XSymbol::RODATA)
			rodata.push_back(i);
		else
			bss.push_back(i);
	}
	if (data.size() > 0ul)
	{
		os << ".data" << std::endl;
		for (auto di : data)
			print_xvar(xsyms_.at(di), os);
		os << std::endl;
	}
	if (rodata.size() > 0ul)
	{
		os << ".section .rodata" << std::endl;
		for (auto di : rodata)
			print_xvar(xsyms_.at(di), os);
		os << std::endl;
	}
	if (bss.size() > 0ul)
	{
		os << ".bss" << std::endl;
		for (auto di : bss)
			print_xvar(xsyms_.at(di), os);
		os << std::endl;
	}
}

const char * HRV64Generator::RV64Asm_header = 
	".option	nopic\n"
	".attribute	arch, \"rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0\"\n"
	".attribute	unaligned_access, 0\n"
	".attribute	stack_align, 16\n"
;

const char * HRV64Generator::RV64Asm_ident =
	".ident	\"HXD1: built at " __TIME__ ", " __DATE__ "\""
;

void HRV64Generator::dump(std::ostream& os)
{
	// print command line arguments
	extern std::vector<std::string> cmd_args;
	if (opts.debug_level != 0)
	{
		os << "# Generated by HXD1:" << std::endl << '#';
		for (auto & arg : cmd_args)
			os << ' ' << arg;
		os << std::endl;
	}
	os << RV64Asm_header << std::endl;
	print_data(os);
	print_functions(os);
	os << std::endl << RV64Asm_ident << std::endl;
}